# from typing import Sequence
# import functools
#
# import quimb as qu
# from quimb.tensor.tensor_core import (
# 	Tensor,
# 	rand_uuid, oset, tags_to_oset,
# )
# from quimb.tensor.tensor_1d import TensorNetwork1DOperator, TensorNetwork1DFlat, MatrixProductOperator
#
# from autoray import do
#
# __all__ = ["PTM_IDM_MPO"]
# _I_o4 = qu.eye(4).reshape(1, 1, 4, 4)
#
# _G0 = do("diag", [1.0, 0, 0, 0], like=None)
# _G1 = do("diag", [0.0, 1, 0, 0], like=None)
# _G2 = do("diag", [0.0, 0, 1, 0], like=None)
# _G3 = do("diag", [0.0, 0, 0, 1], like=None)
#
# _G_o3 = qu.qu([_G0, _G1, _G2, _G3])
#
# def ptm_idm_mat_1qu(p, like=None):
# 	rp = 1 - p
# 	return do("diag", do("array", [1.0, rp, rp, rp], like=like), like=like)
#
# def ptm_idm_mat_2qu(p, like=None):
# 	rp = 1 - p
# 	d = do("array", [1, rp, rp, rp, rp, rp, rp, rp, rp, rp, rp, rp, rp, rp, rp, rp], like=like)
# 	return do("diag", d, like=like)
#
# def ptm_idm_mpo_2qu(
# 		p, i, j,
# 		tags=None,
# 		upper_ind_id="k{}",
# 		lower_ind_id="b{}",
# 		site_tag_id="I{}",
# 		**tn_opts
# ) -> MatrixProductOperator:
# 	i, j = min(i, j), max(i, j)
# 	def gen_arrays():
# 		yield ptm_idm_mat_2qu(p)
# 		for _ in range(i + 1, j):
# 			yield _I_o4
# 		yield _G_o3
# 	return MatrixProductOperator(gen_arrays(),
# 	                             sites=tuple(range(i, j + 1)),
# 	                             tags=tags,
# 	                             upper_ind_id=upper_ind_id,
# 	                             lower_ind_id=lower_ind_id,
# 	                             site_tag_id=site_tag_id,
# 	                             **tn_opts)
#
# class PTM_IDM_MPO(TensorNetwork1DOperator, TensorNetwork1DFlat):
# 	@classmethod
# 	def from_lam_dict(
# 			cls,
# 			ps_1qu: dict[int, ...],
# 			ps_2qu: dict[Sequence[int], ...],
# 			*,
# 			L: int = None,
# 			tags=None,
# 			upper_ind_id: str = "k{}",
# 			lower_ind_id: str = "b{}",
# 			site_tag_id: str = "I{}",
# 			**tn_opts
# 	):
# 		if not isinstance(ps_1qu, dict) and not isinstance(ps_2qu, dict):
# 			raise ValueError
# 		tn = cls.new(L=L,
# 		             upper_ind_id=upper_ind_id,
# 		             lower_ind_id=lower_ind_id,
# 		             site_tag_id=site_tag_id,
# 		             **tn_opts)
# 		tags = tags_to_oset(tags)
#
# 		ptm_idm_mpo_2qu_ = functools.partial(ptm_idm_mpo_2qu,
# 		                                     upper_ind_id=upper_ind_id,
# 		                                     lower_ind_id=lower_ind_id,
# 		                                     site_tag_id=site_tag_id)
# 		for site, p_1qu in ps_1qu.items():
# 			t = Tensor(ptm_idm_mat_1qu(p_1qu), inds=(upper_ind_id.format(site), lower_ind_id.format(site)),
# 			           tags=tags | oset([site_tag_id.format(site)]))
# 			L = max(L, site)
# 			tn.add_tensor(t)
# 		for site, p_2qu in ps_2qu.items():
# 			i, j = site[0], site[1]
# 			t = ptm_idm_mpo_2qu_(p_2qu, i, j, tags=tags)
# 			L = max(L, i, j)
# 			tn.add_tensor_network(t)
# 		return tn
# 	@classmethod
# 	def from_adj_p_vec(
# 			cls,
# 			p_vec_1qu,
# 			p_vec_2qu,
# 			*,
# 			L: int = None,
# 			tags=None,
# 			upper_ind_id: str = "k{}",
# 			lower_ind_id: str = "b{}",
# 			site_tag_id: str = "I{}",
# 			like=None,
# 			**tn_opts
# 	):
# 		"""Initialise a TensorNetwork of Incoherent Depolarizing noise model(IDM) where the
# 		two-qubit IDM parameters are adjacent.
# 		p_vec_1qu: array-like
# 				An array containing the all parameters of one-qubit IDM, where the i-th row indicates
# 				the one-qubit IDM parameters of i-th qubit, e.g.,
# 				``p_vec_1qu = np.array(
# 					[0.01, 0, 0.001, 0], # qubit 0-3
# 				)``
# 		p_vec_2qu: array-like
# 				An array containing the all parameters of two-qubit IDM, where the i-th row indicates
# 				the two-qubit IDM parameters of i-th qubit and (i+1)-th qubit, e.g.,
# 				``p_vec_2qu = np.array(
# 					[0, 0.001, 0.005], # qubit (0, 1), (1, 2), (2, 3)
# 				)``
# 				the number of rows plus 1 should be the same as that of ``lam_mat_1qu``.
# 		L: int, optional
# 				The number of sites the MPO should be defined on. If not given, this is
# 				taken as the row number of ``lam_mat_1qu``.
# 		upper_ind_id: str
# 		lower_ind_id: str
# 		site_tag_id: str
# 		"""
# 		if L is None:
# 			assert (L := len(p_vec_1qu)) == len(p_vec_2qu) + 1
# 		else:
# 			assert L == len(p_vec_1qu) == len(p_vec_2qu) + 1
#
# 		tn = cls.new(L=L,
# 		             upper_ind_id=upper_ind_id,
# 		             lower_ind_id=lower_ind_id,
# 		             site_tag_id=site_tag_id,
# 		             **tn_opts)
# 		tags = tags_to_oset(tags)
#
# 		sites = tuple(range(L))
# 		upper_inds, lower_inds, site_tags = \
# 			map(upper_ind_id.format, sites), map(lower_ind_id.format, sites), map(site_tag_id.format, sites)
#
# 		k1_ix, n_ix = rand_uuid(), rand_uuid()
# 		upper_ind, lower_ind, site_tag = next(upper_inds), next(lower_inds), next(site_tags)
# 		t1 = Tensor(ptm_idm_mat_1qu(p_vec_1qu[0], like=like),
# 		            inds=(k1_ix, lower_ind), tags=tags | oset([site_tag]))
# 		t2 = Tensor(ptm_idm_mat_2qu(p_vec_2qu[0], like=like),
# 		            inds=(n_ix, upper_ind, k1_ix), tags=tags | oset([site_tag]))
# 		tn.add_tensor(t1 @ t2)
# 		p_ix = n_ix
# 		for i in range(1, L - 1):
# 			k1_ix, k2_ix, n_ix = rand_uuid(), rand_uuid(), rand_uuid()
# 			upper_ind, lower_ind, site_tag = next(upper_inds), next(lower_inds), next(site_tags)
# 			t1 = Tensor(ptm_idm_mat_1qu(p_vec_1qu[i], like=like),
# 			            inds=(k1_ix, lower_ind), tags=tags | oset([site_tag]))
# 			if i % 2:
# 				t2 = Tensor(_G_o3,
# 				            inds=(p_ix, k2_ix, k1_ix), tags=tags | oset([site_tag]))
# 				t3 = Tensor(ptm_idm_mat_2qu(p_vec_2qu[i], like=like),
# 				            inds=(n_ix, upper_ind, k2_ix), tags=tags | oset([site_tag]))
# 			else:
# 				t2 = Tensor(ptm_idm_mat_2qu(p_vec_2qu[i], like=like),
# 				            inds=(n_ix, k2_ix, k1_ix), tags=tags | oset([site_tag]))
# 				t3 = Tensor(_G_o3,
# 				            inds=(p_ix, upper_ind, k2_ix), tags=tags | oset([site_tag]))
# 			tn.add_tensor(t1 @ t2 @ t3)
# 			p_ix = n_ix
# 		k1_ix = rand_uuid()
# 		upper_ind, lower_ind, site_tag = next(upper_inds), next(lower_inds), next(site_tags)
# 		t1 = Tensor(ptm_idm_mat_1qu(p_vec_1qu[-1], like=like),
# 		            inds=(k1_ix, lower_ind), tags=tags | oset([site_tag]))
# 		t2 = Tensor(_G_o3,
# 		            inds=(p_ix, upper_ind, k1_ix), tags=tags | oset([site_tag]))
# 		tn.add_tensor(t1 @ t2)
# 		return tn
